<script>
  import $ from '../dfish.js'
  import {
    h
  } from 'vue'
  import FormBase from './FormBase.vue'
  import ButtonBar from './ButtonBar.vue'
  import Button from './Button.vue'
  import mimeType from '../util/mime.js'

  const suffixHooks = {
    '0': 'js.css.pdf.psd.ai.txt.eps.txt',
    xls: 'xls.xlsx',
    doc: 'doc.docx.rtf',
    ppt: 'ppt.pptx',
    htm: 'htm.html',
    zip: 'rar.zip.tar.jar.z.7z.ace.lzh.arj.gzip.bz2.cab.uue',
    fla: 'fla.swf',
    fon: 'eot.otf.fon.font.ttf.ttc.woff.woff2',
    iso: 'iso.nrg.mds.mdf.bin.cue.img.fcd.lcd.dmg',
    vid: 'avi.asf.wmv.avs.flv.mkv.mov.3gp.mp4.webm.mpa.mpe.mpg.mpeg.dat.dsm.ogm.vob.rm.rmvb.ts.tp.ifo.nsv.m4u.m4v.asx.asr.movie.fvi.pvx.rv.vdo.viv.vivo.wm.wmx.wv.wvx.mpv2.mpg4',
    aud: 'aif.aifc.aiff.als.awb.mp3.aac.au.wav.wma.cda.flac.m4a.mid.mka.mp2.mpc.ape.ofr.ogg.ra.wv.tta.ac3.dts.wv.shn.vqf.spc.nsf.adx.psf.minipsf.psf2.minipsf2.rsn.zst',
    img: 'jpg.jpeg.gif.png.bmp.tif.tiff.ico'
  }

  const suffix = (function() {
    let r = {}
    for (let k in suffixHooks) {
      suffixHooks[k].split('.').forEach(v => r[v] = k == '0' ? v : k)
    }
    return r
  })()

  const getSuffix = function(url) {
    let a = $.strFrom(url || '', '.', true).toLowerCase()
    return suffix[a] || 'file'
  }

  // 把 "1MB" 转为字节
  const fileByte = function(val) {
    let unit
    if (val != null && !isNaN(val))
      val += 'K'
    val = $.number(val.replace(/[a-z]+$/i, function($0) {
      unit = $0.charAt(0).toUpperCase();
      return ''
    }))
    return val * (unit === 'K' ? 1024 : unit === 'M' ? 1024 * 1024 : unit === 'G' ? 1024 * 1024 * 1024 : unit ===
      'T' ? 1024 * 1024 * 1024 * 1024 : 1)
  }

  const fileTypeHooks = {
    video: suffixHooks.vid,
    audio: suffixHooks.aud,
    image: suffixHooks.img
  }

  export default {
    name: 'w-fileupload',
    mixins: [FormBase],
    props: ['uploadSrc', 'thumbnailSrc', 'downloadSrc', 'uploadLimit', 'sizeLimit', 'maxFileSize', 'minFileSize',
      'fileTypes', 'filter', 'success', 'error', 'itemWidth', 'click:item', 'vertical',
    ],
    emits: ['click:item'],
    data() {
      return {
        list: [],
        emiting: false,
        dir: 'v',
      }
    },
    created() {
      this.$watch(() => this.x.modelValue, v => {
        if (this.emiting) return this.emiting = false
        this.list = Array.isArray(v) ? v.map(value => {
          return {
            value
          }
        }) : v ? [{
          value: v
        }] : []
      }, {
        immediate: true
      })
    },
    methods: {
      val(v) {
        if (arguments.length == 0) {
          let r = []
          this.list.forEach(item => item.value && r.push(item.value))
          return r
        }
        this.x.modelValue = v
        this.trigger('update:modelValue', v)
        this.trigger('change', this, v)
      },
      getFileTypes() {
        let t = this.x.fileTypes
        return t && t !== '*.*' ? t.replace(/\*/g, '').replace(/;/g, ',').toLowerCase() : ''
      },
      getAccept() {
        let r = []
        let t = this.getFileTypes()
        if (!t) return
        let n = []
        t.toLowerCase().split(',').forEach(b => {
          if (!b) return
          n.push(fileTypeHooks[b] || b)
        })
        n.forEach(b => {
          b && r.push(mimeType(b) || b)
        })
        return r.join(',')
      },
      isImage(item) {
        let s = getSuffix(item.value ? item.value.name : item.file.name)
        return s == 'img'
      },
      isVideo(item) {
        let s = getSuffix(item.value ? item.value.name : item.file.name)
        return s == 'vid'
      },
      isAudio(item) {
        let s = getSuffix(item.value ? item.value.name : item.file.name)
        return s == 'aud'
      },
      fileSelected(e) {
        if (this.x.uploadLimit > 1 && e.target.files.length + this.list.length > this.x.uploadLimit) {
          return $.alert($.loc.ps($.loc.form.upload_limit, this.x.uploadLimit))
        }
        let xfs = this.x.maxFileSize && fileByte(this.x.maxFileSize)
        let nfs = this.x.minFileSize && fileByte(this.x.minFileSize)
        let ft = this.getFileTypes()
        let xfs_names
        let nfs_names
        let ft_names
        $.lodash.each(e.target.files, file => {
          let sfx = '.' + $.strFrom(file.name, '.', true).toLowerCase()
          if (xfs && file.size > xfs) {
            (xfs_names || (xfs_names = [])).push(file.name)
          } else if (nfs && file.size < nfs) {
            (nfs_names || (nfs_names = [])).push(file.name)
          } else if (ft && !$.idsAny(ft, sfx, ',')) {
            (ft_names || (ft_names = [])).push(file.name)
          } else {
            this.list.push({file})
          }
        })
        if (xfs_names) {
          $.alert($.loc.ps($.loc.form.upload_maxfilesize, xfs_names.join('<br>'), this.x.maxFileSize))
        } else if (nfs_names) {
          $.alert($.loc.ps($.loc.form.upload_minfilesize, nfs_names.join('<br>'), this.x.minFileSize))
        } else if (ft_names) {
          $.alert($.loc.ps($.loc.form.upload_invalid_file_type, ft_names.join('<br>')))
        }
        e.target.value = '' // 获取文件后要设value为空，否则下一次选择同一个文件不会触发onchange
        this.nextUpload()

      },
      upload(item) {
        if (this.x.uploadSrc && !item.loading) {
          let src = $.urlFormat(this.x.uploadSrc, this.x.data, this.x)
          let fd = new FormData()
          fd.append('Filedata', item.file)
          item.loading = true
          item.source = $.http.CancelToken.source()
          $.http.post(src, fd, {
            onUploadProgress: e => item.progress = Math.floor(e.loaded / e.total * 100),
            cancelToken: item.source.token
          }).then(res => {
            item.loading = false
            item.loaded = true
            item.source = null
            let d = this.x.filter ? this.x.filter(res, this) : res.data
            if (!d) {
              item.error = {
                message: $.loc.form.upload_no_data
              }
            } else if (d.error) {
              item.error = d.error
            } else if (!d.id) {
              item.error = {
                message: $.loc.ps($.loc.form.upload_invalid_data, JSON.stringify(d))
              }
            } else {
              item.value = d
              this.x.success && this.x.success(item.value, this, res)
            }
            this.nextUpload()
            if (item.error && item.error.message) $.alert(item.error.message)
          }).catch(error => {
            item.error = error
            item.loading = false
            item.loaded = true
            item.source = null
            if (error.response && error.response.status) $.alert(error.response.status + ': ' + error)
          })
        }
      },
      nextUpload() {
        for (let item of this.list) {
          if (item.loading) return
          if (item.file && !item.loaded) {
            return this.upload(item)
          }
        }
        this.emiting = true
        let v = this.val()
        this.$emit('update:modelValue', v)
        this.$emit('change', v)
      },
      clickItem(e, item) {
        if (!item.value) return
        if (this.getOn('click:item')) {
          e.runType = 'click:item'
          this.trigger(e, item.value)
        } else if (this.x.downloadSrc) {
          $.download($.urlServer(this.x.downloadSrc, item.value, this.x.data))
        }
      },
      remove(item) {
        item.source && item.source.cancel()
        let i = this.list.findIndex(v => v == item)
        this.list.splice(i, 1)
        this.nextUpload()
      },
      formText() {
        let v = this.val()
        return v.map(m => m.name).join(', ')
      },
      chooseFile() {
        this.$refs.file.click()
      },
      html_list() {
        return this.list.map(item => {
          let n = item.value ? item.value.name : item.file.name
          return h('div', {
            class: ['w-fileupload-value', {
              'z-loading': item.loading,
              'z-err': item.error
            }],
            style: {
              '--w-upload-progress': item.file ? `${item.progress || 0}%` : '',
              width: $.unitSize(this.x.itemWidth)
            },
            title: item.error ? item.error.message : n,
            onclick: e => this.clickItem(e, item)
          }, [
            h('i', {
              class: ['w-fileupload-value-icon f-i', 'f-i-file-' + getSuffix(n)]
            }),
            item.error && h('div', {
              class: 'w-fileupload-value-error'
            }, $.loc.form.upload_error),
            h('div', {
              class: 'w-fileupload-value-text f-line-1'
            }, n), h('div', {
              class: 'w-fileupload-value-close f-i f-i-trash',
              title: $.loc.remove,
              onclick: e => {
                this.remove(item), e.stopPropagation()
              }
            })
          ])
        })
      },
      html_input_form() {
        return [
          this.html_input_hidden(),
          h('input', {
            type: 'file',
            ref: 'file',
            multiple: this.x.uploadLimit != 1,
            accept: this.getAccept(),
            onchange: this.fileSelected
          })
        ]
      },
      html_input_button() {
        return h(ButtonBar, {
          ref: 'uploadbbr',
          class: 'w-fileupload-bbr',
          gap: 10,
        }, {
          default: () => [this.$slots.button ? this.$slots.button() : h(Button, {
            text: '选择文件',
            icon: '.f-i .f-i-upload',
            class: 'w-fileupload-button',
            onclick: this.chooseFile
          })]
        })
      },
      html_input_list() {
        return this.list.length ? h('div', {
          ref: 'list',
          class: ['w-fileupload-list', {[`f-${this.x.vertical?'v':'h'}-gap`]: $.br.compat.gap}]
        }, this.html_list()) : !this.isNormal ? h('div', {
          class: 'w-fileupload-nofile f-flex-align'
        }, $.loc.form.no_files) : null
      },
      html_input() {
        return this.html_input_form().concat(this.canUpload && this.html_input_button(), this.html_input_list())
      },

    },
    computed: {
      validHooks() {
        return {
          ...this.pubValidHooks,
          validateDefault: (v) => {
            for (let item of this.list) {
              if (item.loading) {
                return this.formErr({type: 'upload_loading'})
              }
              if (item.error) {
                return this.formErr({type: 'upload_invalid_file'})
              }
            }
          }
        }
      },
      input_value() {
        let v = this.val()
        return v.length == 0 ? '' : JSON.stringify(v)
      },
      errorTipTarget() {
        return this.$refs.uploadbbr || this
      },
      canUpload() {
        if (!this.isNormal) return false
        if (this.x.uploadLimit > 0 && this.list.length >= this.x.uploadLimit) return false
        return true
      },
      html_class() {
        return {'z-vertical': this.x.vertical}
      },
      html_style() {
        return {
          '--w-label-min-height': '32px',
        }
      },
      html_on() {
        return {
          onclick: () => this.warn(false)
        }
      }
    }
  }
</script>

<style>
  .w-fileupload {
    align-items: flex-start;
  }

  .w-fileupload>.w-label {
    min-height: var(--w-label-min-height);
  }

  .w-fileupload>.w-input {
    flex-direction: column;
    align-items: flex-start;
    --w-compat-gap: 10px;
    gap: var(--w-compat-gap);
  }

  .w-fileupload input[type="file"],
  .w-mediaupload input[type="file"] {
    display: none;
  }

  .w-fileupload-list {
    display: flex;
    flex-wrap: wrap;
    width: 100%;
    --w-compat-gap: 10px;
    gap: var(--w-compat-gap);
  }
  
  .w-fileupload.z-vertical .w-fileupload-list {
    flex-direction: column;
  }

  .w-fileupload-button {
    background: #f8f8f8;
  }

  .w-fileupload-value {
    display: flex;
    align-items: center;
    cursor: pointer;
    box-sizing: border-box;
    height: 32px;
    border: 1px solid #ddd;
    background: #f8f8f8;
    overflow: hidden;
  }

  .w-fileupload-value.z-loading {
    background: linear-gradient(to right, #e5f3ff var(--w-upload-progress), #fff var(--w-upload-progress));
  }

  .w-fileupload-value.z-err {
    border-color: #f00;
  }

  .w-fileupload-value-icon {
    width: 30px;
    height: 30px;
    margin-right: 10px;
    flex-shrink: 0;
  }

  .w-fileupload-value-error {
    margin-right: 10px;
    color: #f00;
  }

  .w-fileupload-value-text {
    flex: 1;
    margin-right: 15px;
    max-width: 100%;
  }

  .w-fileupload-value-text:hover {
    text-decoration: underline;
  }

  .w-fileupload.z-ds .w-fileupload-value-close {
    display: none;
  }

  .w-fileupload-value-close {
    padding-right: 8px;
    color: #999;
  }

  .w-fileupload-value-close:hover {
    color: var(--w--focus-color);
  }

  .w-fileupload-nofile {
    color: #999;
    height: 32px;
  }

  .w-fileupload.z-err .w-fileupload-button {
    border-color: #f00;
  }
</style>
